local addon = LibStub("AceAddon-3.0"):GetAddon("Poppins")

local LibMacroParser = LibStub("LibMacroParser-1.0")
local LibButtonFacade = LibStub("LibButtonFacade", true)
local LibStickyFrames = LibStub("LibStickyFrames-2.0")
local LibKeyBound = LibStub("LibKeyBound-1.0")
local LibSharedMedia = LibStub("LibSharedMedia-3.0")

local count = 0
local weak = {__mode='k'}
local cache = setmetatable({}, weak)
local used = setmetatable({}, weak)

local Popup = {}
addon.Popup = Popup
local prototype = setmetatable(CreateFrame("CheckButton"), addon.Button.metatable)
LibStub("AceEvent-3.0"):Embed(prototype)
prototype.callbacks = LibStub("CallbackHandler-1.0"):New(prototype)
Popup.prototype = prototype
Popup.metatable = { __index = prototype }
Popup.callbacks = LibStub("CallbackHandler-1.0"):New(Popup)

function Popup:Retrieve(id, parent, actions)
	local button = next(cache)
	if button then
		cache[button] = nil
	else
		count = count + 1
		local rid = count
		local header = addon.header
		local buttonName = format("%sPopup%d", addon.name, rid)
		button = setmetatable(CreateFrame("CheckButton", buttonName, header, "SecureAnchorUpDownTemplate,SecureActionButtonTemplate,ActionButtonTemplate"), addon.Popup.metatable)
		button.state = ""
		button.rid = rid
		button:SetWidth(36)
		button:SetHeight(36)

		button.icon = _G[format("%sIcon", buttonName)]
		button.border = _G[format("%sBorder", buttonName)]
		button.cooldown = _G[format("%sCooldown", buttonName)]
		button.macroName = _G[format("%sName", buttonName)]
		button.hotkey = button:CreateFontString(format("%sHotKey", buttonName), "ARTWORK", "NumberFontNormal") -- _G[format("%sHotKey", buttonName)]
		button.count = _G[format("%sCount", buttonName)]
		button.flash = _G[format("%sFlash", buttonName)]
		button.flash:Hide()

		header:SetAttribute("addchild", button)

		button:SetAttribute("checkselfcast", true)
		button.setActions = {}
		button.attributes = {}
		button:HookScript("OnEnter", button.OnEnter)
		button:HookScript("OnLeave", button.OnLeave)
		button:SetScript("OnAttributeChanged", button.OnAttributeChanged)

		button:RegisterForDrag("LeftButton", "RightButton")
	end
	used[button] = true

	if id or parent then button:SetID(id, parent) end
	if actions then button:SetActions(actions) end

	return button
end

function Popup:Iterate()
	return pairs(used)
end

function prototype:Store()
	used[self] = nil
	self:SetActions()
	self:SetID()
	cache[self] = true
end

function prototype:SetID(id, parent)
	self.id = id
	self.parent = parent

	if id and parent then
		LibStickyFrames:RegisterFrame(self)
		LibStickyFrames:SetFrameInsets(self, -3, -2, -2, -3)
		LibStickyFrames:SetFrameGroup(self, parent)
		LibStickyFrames:SetFrameText(self, format("%d-%d", parent.id, id))
	
		self:SetScript("OnShow", self.OnShow)
		self:SetScript("OnHide", self.OnHide)
		self:SetScript("OnDragStart", self.OnDragStart)
		self:SetScript("OnReceiveDrag", self.OnReceiveDrag)
		self:SetScript("PreClick", self.PreClick)
		self:SetScript("PostClick", self.PostClick)
		self:SetScript("OnEnter", self.OnEnter)
		self:SetScript("OnLeave", self.OnLeave)
		self:RegisterAllEvents()
	
		self:SetAttribute("anchorchild", parent)
		self:SetAttribute("showstates", format("-1,%d", parent.id))
		parent:SetAttribute(format("statemap-anchor-%d", id), id)

		self:UpdateOptions()
		if self.actions then self:UpdateActions() end
	else
		if self.actions then self:UpdateActions() end
		self:UpdateOptions()

		LibStickyFrames:UnregisterFrame(self)
		self:UnregisterAllEvents()
		self:SetScript("OnShow", nil)
		self:SetScript("OnHide", nil)
		self:SetScript("OnDragStart", nil)
		self:SetScript("OnReceiveDrag", nil)
		self:SetScript("OnUpdate", nil)
		self:SetScript("PreClick", nil)
		self:SetScript("PostClick", nil)
		self:SetScript("OnEnter", nil)
		self:SetScript("OnLeave", nil)
		LibMacroParser.UnregisterAllCallbacks(self)

		for name, value in pairs(self.attributes) do
			self:SetAttribute(name, nil)
		end
	end

	Popup.callbacks:Fire("OnSetID", self, id, parent)
end

local buttons = { "LeftButtonUp", "RightButtonUp", "MiddleButtonUp" }
function prototype:UpdateOptions()
	local id = self.id
	local parent = self.parent
	local options = parent and parent.options

	if id and parent and options then
		LibButtonFacade = LibButtonFacade or LibStub("LibButtonFacade", true)
		if LibButtonFacade then
			LibButtonFacade:Group(addon.name, parent:GetGroup() or parent.id):AddButton(self)
		end
		self:SetScale(options.popupscale)
		self:SetAlpha(options.alpha * options.popupalpha)
		self:SetFrameStrata(options.popupstrata)
		self:UpdatePosition()

		self:SetLock(addon:IsLocked())

		local clicks
		for i=1, 3 do
			local button = options.clicks[i]
			if button == 1 or button == 2 then
				clicks = clicks and strjoin(",", clicks, buttons[i]) or buttons[i]
			end
			if button == 1 or button == 3 then
				self:SetAttribute(format("onmouseupbutton%d", i), format("mub%d", i))
				self:SetAttribute(format("*childstate-mub%d", i), id)
			end
		end
		self:RegisterForClicks(strsplit(",", clicks or ""))

		local hotkey = self.hotkey
		if options.hotkey.show then
			hotkey:SetJustifyH(options.hotkey.justify)
			hotkey:ClearAllPoints()
			hotkey:SetPoint(options.hotkey.justify, self, "CENTER", options.hotkey.x, options.hotkey.y)
			hotkey:SetFont(LibSharedMedia:Fetch("font", options.hotkey.font), options.hotkey.size, "OUTLINE")
			hotkey:Show()
		else
			hotkey:Hide()
		end
		local count = self.count
		if options.count.show then
			count:SetJustifyH(options.count.justify)
			count:ClearAllPoints()
			count:SetPoint(options.count.justify, self, "CENTER", options.count.x, options.count.y)
			count:SetFont(LibSharedMedia:Fetch("font", options.count.font), options.count.size, "OUTLINE")
			count:Show()
		else
			count:Hide()
		end
	else
		self.count:Hide()
		self.hotkey:Hide()

		self:RegisterForClicks(nil)

		self:SetFrameStrata("BACKGROUND")
		self:SetScale(1)

		LibStickyFrames:SetFramePoints(self)
		self:ClearAllPoints()
		self:Hide()

		LibButtonFacade = LibButtonFacade or LibStub("LibButtonFacade", true)
		if LibButtonFacade then
			LibButtonFacade:Group(addon.name, parent:GetGroup() or parent.id):RemoveButton(self)
		end
	end
end

function prototype:GetCursorInfo()
	local type, value, value2 = GetCursorInfo()
	if type == "spell" then
		local spell, rank, maxRank
		spell, rank = GetSpellName(value, value2)
		spell, maxRank = GetSpellName(spell)
		if rank == maxRank then
			value = spell
		else
			value = format("%s(%s)", spell, rank or "")
		end
	elseif type == "item" then
		value = value2:match("item:%d+:%d+:%d+:%d+:%d+:%d+:%d+:%d+")
	end
	return type, value
end

function prototype:OnDragStart()
	if InCombatLockdown() or addon.db.profile.lock then return end

	local modifier = self:GetModifier()
	self:Pickup(modifier)

	local type = self:GetAction("type", modifier)
	self:SetAction("type", modifier, nil)
	self.actions[modifier or ""]["type"] = nil
	if type then
		self:SetAction(type, modifier, nil)
		self.actions[modifier or ""][type] = nil
	end

	self:Update()
end

function prototype:OnReceiveDrag()
	local type, value = self:GetCursorInfo()
	if not type or not value then
		return
	elseif InCombatLockdown() then
		return addon:Print(ERR_NOT_IN_COMBAT)
	end

	ClearCursor()
	local modifier = self:GetModifier()
	self:Pickup(modifier)

	self.actions[modifier or ""]["type"] = type
	self:SetAction("type", modifier, type)
	if type then
		self.actions[modifier or ""][type] = value
		self:SetAction(type, modifier, value)
	end

	self:Update()
end

function prototype:Update()
	self:Super("Update")
	if self:IsSelectedState() then
		self.parent.button:Update()
	end
end

function prototype:Pickup(modifier)
	local type = self:GetAction("type", modifier)
	if not type then return end
	local value = self:GetAction(type, modifier)
	if not value then
		return
	elseif type == "spell" then
		PickupSpell(value)
	elseif type == "item" then
		PickupItem(value)
	elseif type == "macro" then
		PickupMacro(value)
	end
end

function prototype:GetModifier()
    local prefix = false
    if IsShiftKeyDown() then
        prefix = format("shift-%s", prefix or "")
    end
    if IsControlKeyDown() then
        prefix = format("ctrl-%s", prefix or "")
    end
    if IsAltKeyDown() then
        prefix = format("alt-%s", prefix or "")
    end
	return prefix
end

do
	local type, value
	function prototype:PreClick()
		type, value = self:GetCursorInfo()
	end

	function prototype:PostClick()
		if not type or not value then
			return self:UpdateState()
		elseif InCombatLockdown() then
			self:UpdateState()
			addon:Print(ERR_NOT_IN_COMBAT)
			return
		end

		ClearCursor()
		local modifier = self:GetModifier()
		self:Pickup(modifier)

		self.actions[modifier or ""]["type"] = type
		self:SetAction("type", modifier, type)
		if type then
			self.actions[modifier or ""][type] = value
			self:SetAction(type, modifier, value)
		end

		self:Update()
	end
end

function prototype:SetLock(locked)
	LibStickyFrames:SetFrameHidden(self, not locked)
	self:EnableMouse(not locked)

	Popup.callbacks:Fire("OnSetLock", self, locked)
end

local relPoints = { TOP = "BOTTOM", BOTTOM = "TOP", LEFT = "RIGHT", RIGHT = "LEFT", CENTER = "CENTER" }
function prototype:UpdatePosition()
	local id = self.id - 1
	local parent = self.parent
	local settings = parent and parent.settings
	local options = parent and parent.options
	local direction, padding = options and options.direction, options and options.padding
	local x, y = 0, 0

	if direction == "center" then
		LibStickyFrames:SetFramePoints(self)
		local count = settings.popups.count
		local offset = math.fmod(options.offset or 0, 360)

		local angle = math.rad(((360 / count) * id) + offset)
		x = math.sin(angle) * (padding + self:GetWidth())
		y = math.cos(angle) * (padding + self:GetHeight())
		self:ClearAllPoints()
		self:SetPoint(relPoints[direction], parent, direction, x, y)
	else
		if direction == "top" or direction == "bottom" then
			y = padding
		else
			x = padding
		end
		if direction == "bottom" or direction == "left" then
			x = x * -1
			y = y * -1
		end
		LibStickyFrames:SetFramePoints(self, relPoints[direction], parent.popups[id] or parent, direction, x, y)
	end

	local state = addon.header:GetAttribute("state")
	if state == "-1" or state == tostring(parent.id) then
		self:Show()
	else
		self:Hide()
	end
end

function prototype:IsSelectedState()
	local id = self.id
	local parent = self.parent
	return id and parent and parent:GetAttribute("state") == tostring(id)
end

function prototype:UpdateHotkeys()
	local key
	local type = self.type
	if type and SecureButton_GetAttribute(self, type) ~= SecureButton_GetModifiedAttribute(self, type) then
		key = LibKeyBound:ToShortKey(self:GetModifier())
	end
	self.hotkey:SetText(key)
end

function prototype:UpdateSelected()
end

function prototype:GetAction(action, modifier)
	return self:GetAttribute(modifier or "*", action, "*")
end

function prototype:SetAction(action, modifier, value)
	local id = self.id
	local parent = self.parent
	local setActions = self.setActions

	if value then
		setActions[modifier] = setActions[modifier] or {}
	end
	if setActions[modifier] then
		setActions[modifier][action] = value
	end
	self:SetAttribute(format("%s%s*", modifier or "*", action), value)
	parent.button:SetAttribute(format("%s%s-%d", modifier or "*", action, id), value)

	Popup.callbacks:Fire("OnSetAction", self, action, modifier, value)
end

function prototype:SetActions(actions)
	self.actions = actions
	self:UpdateActions()
end

function prototype:UpdateActions()
	local id = self.id
	local parent = self.parent
	local actions = self.actions

	LibMacroParser.UnregisterAllCallbacks(self)
	if id and parent and actions then
		for modifier, attributes in pairs(actions) do
			modifier = modifier ~= "" and modifier or false
			for action, value in pairs(attributes) do
				self:SetAction(action, modifier, value)
			end
		end
	else
		for modifier, attributes in pairs(self.setActions) do
			for action, value in pairs(attributes) do
				self:SetAction(action, modifier, nil)
			end
		end
	end

	self:Update()
end